﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace EditMode.Diva2nd
{
    public partial class Edit
    {
        // BodyName = "iso-2022-jp" CodePage = 932
        private readonly Encoding Encoding = Encoding.GetEncoding(932); 

        public const int BEATS_PER_BAR = 8;

        public const int BAR_COUNT = 600;

        public const int LYRICS_LENGTH = 0x29;

        public string EditName { get; set; } = string.Empty;

        public string SongName { get; set; } = string.Empty;

        public float StartTime { get; set; } = 0f;

        public Module Module1 { get; set; } = Module.none;

        public Module Module2 { get; set; } = Module.none;

        public TimeSpan StartTimeSpan => TimeSpan.FromSeconds(StartTime);

        public int SongEndBeat { get; set; } = BAR_COUNT * BEATS_PER_BAR;

        public SeName ButtonSound { get; set; } = SeName.no_button;

        public List<Stage> Stages { get; set; } = new List<Stage>();

        public List<Effect> Effects { get; set; } = new List<Effect>();

        public List<HandItem> HandItems { get; set; } = new List<HandItem>();

        public List<Motion> Motions { get; set; } = new List<Motion>();

        public List<string> Lyrics { get; set; } = new List<string>(BAR_COUNT);

        public List<(int bpm, int bar)> BpmChanges { get; set; } = new List<(int bpm, int bar)>(BAR_COUNT);

        public List<(int sig, int bar)> SigChanges { get; set; } = new List<(int sig, int bar)>(BAR_COUNT);

        public List<EditPvCommand> PvCommands { get; set; } = new List<EditPvCommand>();

        private List<int> changeBars = new List<int>();

        public TimeSpan[] TimelineTimes { get; private set; }

        public Edit()
        {
            return;
        }

        public Edit(string filePath)
        {
            LoadFromFile(filePath);
            CalculateTimelineTimes();
        }

        public void CalculateTimelineTimes()
        {
            TimelineTimes = new TimeSpan[BAR_COUNT * BEATS_PER_BAR];

            double time = 0;
            for (int beat = 1; beat < TimelineTimes.Length; beat++)
            {
                double bpm = GetBpmAt(beat);
                double increment = (240.0 / bpm * 1000.0) / BEATS_PER_BAR;
                time += increment;
                TimelineTimes[beat] = TimeSpan.FromMilliseconds(double.IsInfinity(time) ? 0 : time);
            }
        }

        public float GetBpmAt(int totalBeat)
        {
            if (BpmChanges.Count < 1)
                return 0;

            if (BpmChanges.Count == 1)
                return BpmChanges[0].bpm;

            int bpm = BpmChanges[0].bpm;
            for (int i = 0; i < BpmChanges.Count; i++)
            {
                int bar = (totalBeat - 1) / BEATS_PER_BAR;
                if (BpmChanges[i].bar <= bar)
                {
                    bpm = BpmChanges[i].bpm;
                }
                else
                {
                    break;
                }
            }
            return bpm;

            //if (BpmChanges.Count == 0)
            //{
            //    return 0;
            //}
            //else if (BpmChanges.Count == 1)
            //{
            //    return BpmChanges[0].bpm;
            //}

            //int bar = totalBeat / BEATS_PER_BAR;
            //return BpmChanges.LastOrDefault(b => b.bar >= bar).bpm;
        }

        public TimeSpan GetTimeAt(int totalBeat)
        {
            try
            {
                if (TimelineTimes == null)
                    return TimeSpan.Zero;

                if (totalBeat < 0)
                {
                    totalBeat = 0;
                }
                else if (totalBeat > TimelineTimes.Length - 1)
                {
                    totalBeat = TimelineTimes.Length - 1;
                }

                return TimelineTimes[totalBeat];
            }
            catch (Exception)
            {
                return TimeSpan.Zero;
            }
        }

        private void LoadFromFile(string filePath)
        {
            LoadFromStream(File.OpenRead(filePath));
        }

        private void LoadFromStream(Stream stream)
        {
            using (var reader = new BinaryReader(stream))
            {
                const int DIVA_SCRIPT_OFFSET = 0xE108;
                reader.Seek(DIVA_SCRIPT_OFFSET);
                ParsePvCommands(reader);

                const int LYRICS_OFFSET = 0x2C2AC;
                reader.Seek(LYRICS_OFFSET);
                ParseLyrics(reader);

                const int BPM_OFFSET = 0x337DC;
                reader.Seek(BPM_OFFSET);
                ParseBpmChanges(reader);
                ParseSigChanges(reader);

                const int MODULE_OFFSET = 0x35400 - 4;
                reader.Seek(MODULE_OFFSET);
                SongEndBeat = reader.ReadInt32() * BEATS_PER_BAR;
                Module1 = (Module)reader.ReadInt32();
                Module2 = (Module)reader.ReadInt32();

                const int STAGE_OFFSET = 0x35408;
                reader.Seek(STAGE_OFFSET);
                ParseStages(reader);

                ParseMotions(reader);

                ButtonSound = (SeName)reader.ReadInt32();

                ParseHandItems(reader);

                ParseEffects(reader);

                ParseEditInfo(reader);
            }
        }

        private void ParsePvCommands(BinaryReader reader)
        {
            for (int i = 0; i < short.MaxValue; i++)
            {
                Opcode nextOpcode = (Opcode)reader.ReadInt32();

                if (!Enum.IsDefined(typeof(Opcode), nextOpcode))
                    return;

                PvCommands.Add(new EditPvCommand(reader, nextOpcode));
            }
        }

        private void ParseLyrics(BinaryReader reader)
        {
            for (int i = 0; i < BAR_COUNT; i++)
            {
                string lyric = ParseString(reader, LYRICS_LENGTH);

                if (!string.IsNullOrEmpty(lyric))
                    Lyrics.Add(lyric);
            }
        }

        private void ParseBpmChanges(BinaryReader reader)
        {
            List<int> bpms = new List<int>(BAR_COUNT);

            for (int i = 0; i < BAR_COUNT; i++)
            {
                int bpm = reader.ReadInt32();

                if (bpm != -1)
                    bpms.Add(bpm);
            }

            for (int i = 0; i < BAR_COUNT; i++)
            {
                int bar = reader.ReadInt32();

                if (bar != -1)
                    changeBars.Add(bar);
            }

            for (int i = 0; i < bpms.Count; i++)
            {
                BpmChanges.Add((bpms[i], changeBars[i]));
            }
        }

        private void ParseSigChanges(BinaryReader reader)
        {
            for (int i = 0; i < BAR_COUNT; i++)
            {
                int sig = reader.ReadInt32();

                if (sig != -1)
                    SigChanges.Add((sig, changeBars[i]));
            }
        }

        private void ParseStages(BinaryReader reader)
        {
            const int STAGE_COUNT = 20;

            for (int i = 0; i < STAGE_COUNT; i++)
            {
                int stage = reader.ReadInt32();

                if (stage != -1)
                    Stages.Add((Stage)stage);
            }
        }

        private void ParseMotions(BinaryReader reader)
        {
            const int MOTION_COUNT = 30;

            for (int i = 0; i < MOTION_COUNT; i++)
            {
                int motion = reader.ReadInt32();

                if (motion != -1)
                    Motions.Add((Motion)motion);
            }
        }

        private void ParseHandItems(BinaryReader reader)
        {
            const int HAND_ITEM_COUNT = 9;

            for (int i = 0; i < HAND_ITEM_COUNT; i++)
            {
                int handItem = reader.ReadInt32();

                if (handItem != -1)
                    HandItems.Add((HandItem)handItem);
            }
        }

        private void ParseEffects(BinaryReader reader)
        {
            const int EFFECT_COUNT = 11;

            for (int i = 0; i < EFFECT_COUNT; i++)
            {
                int effect = reader.ReadInt32();

                if (effect != -1)
                    Effects.Add((Effect)effect);
            }
        }

        private void ParseEditInfo(BinaryReader reader)
        {
            const int NAME_LENGTH = 0x28;

            EditName = ParseString(reader, NAME_LENGTH);
            int editNameLength = reader.ReadInt32();

            SongName = ParseString(reader, NAME_LENGTH);
            int songNameLength = reader.ReadInt32();

            StartTime = reader.ReadSingle();
        }

        private string ParseString(BinaryReader reader, int length)
        {
            return Encoding.GetString(reader.ReadBytes(length)).TrimEnd('\0');
        }
    }
}
